package com.project.thisDatabase.elasticsearch.convert;

import com.project.annotation.data.elasticsearch.ElasticHighlight;
import com.project.annotation.data.elasticsearch.ElasticQueryItem;
import com.project.annotation.enums.elasticsearch.AnalyzerType;
import com.project.annotation.enums.elasticsearch.ItemType;
import com.project.annotation.enums.elasticsearch.OperType;
import com.project.annotation.enums.elasticsearch.SearchType;
import com.project.annotation.function.ConvertValueItem;
import com.project.annotation.model.ConvertContext;
import com.project.annotation.model.EntityCollectItem;
import com.project.thisDatabase.elasticsearch.model.RangeItem;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;

import java.util.ArrayList;
import java.util.List;

public class QueryConvert implements ConvertValueItem {

    public BoolQueryBuilder boolQueryBuilder;
    public BoolQueryBuilder boolFilterBuilder;

    private SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    private List<String> excludes = new ArrayList<>();
    private HighlightBuilder highlightBuilder;

    protected BoolQueryBuilder createQueryBuilder(String type){
        if (SearchType.FILTER.equals(type)){
            if (null == boolFilterBuilder){
                boolFilterBuilder = QueryBuilders.boolQuery();
            }
            return boolFilterBuilder;
        }

        if (null == boolQueryBuilder){
            boolQueryBuilder = QueryBuilders.boolQuery();
        }
        return boolQueryBuilder;
    }

    protected HighlightBuilder getHighlightBuilder() {
        if (null == highlightBuilder) {
            highlightBuilder = new HighlightBuilder();
        }

        return highlightBuilder;
    }

    protected void bindOper(ElasticQueryItem elasticQueryItem, Object val){
        if (ItemType.TERM.equals(elasticQueryItem.type())){
            BoolQueryBuilder bool = createQueryBuilder(elasticQueryItem.search());
            if (OperType.MUST.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    for (Object it : (List)val) {
                        boolQueryBuilder.should(QueryBuilders.termQuery(elasticQueryItem.name(), it).boost(elasticQueryItem.boost()));
                    }
                    bool.must(boolQueryBuilder);
                } else {
                    bool.must(QueryBuilders.termQuery(elasticQueryItem.name(), val).boost(elasticQueryItem.boost()));
                }
            } else if (OperType.SHOULD.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    for (Object it : (List)val)
                        bool.should(QueryBuilders.termQuery(elasticQueryItem.name(), it).boost(elasticQueryItem.boost()));
                } else {
                    bool.should(QueryBuilders.termQuery(elasticQueryItem.name(), val).boost(elasticQueryItem.boost()));
                }
            } else if (OperType.NOT.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    for (Object it : (List)val) {
                        boolQueryBuilder.should(QueryBuilders.termQuery(elasticQueryItem.name(), it).boost(elasticQueryItem.boost()));
                    }
                    bool.mustNot(boolQueryBuilder);
                } else {
                    bool.mustNot(QueryBuilders.termQuery(elasticQueryItem.name(), val).boost(elasticQueryItem.boost()));
                }
            } else if (OperType.RANGE.equals(elasticQueryItem.operType())) {
                if (val instanceof RangeItem) {
                    RangeItem range = (RangeItem) val;
//                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(elasticQueryItem.name()).boost(elasticQueryItem.boost());
                    if (null != range.getFrom()) {
                        rangeQueryBuilder.from(range.getFrom().getValue());
                    }

                    if (null != range.getTo()) {
                        rangeQueryBuilder.to(range.getTo().getValue());
                    }

                    if (OperType.SHOULD.equals(range.getLinkType())) {
                        bool.should(rangeQueryBuilder);
                    } else if (OperType.NOT.equals(range.getLinkType())) {
                        bool.mustNot(rangeQueryBuilder);
                    } else {
                        bool.must(rangeQueryBuilder);
                    }
                }
            }
        } else if (ItemType.MATCH.equals(elasticQueryItem.type())){
            BoolQueryBuilder bool = createQueryBuilder(elasticQueryItem.search());
            if (OperType.MUST.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    for (Object it : (List)val) {
                        boolQueryBuilder.should(QueryBuilders.termQuery(elasticQueryItem.name(), it).boost(elasticQueryItem.boost()));
                    }
                    bool.must(boolQueryBuilder);
                } else {
                    bool.must(QueryBuilders.matchQuery(elasticQueryItem.name(), val).analyzer(AnalyzerType.smart).boost(elasticQueryItem.boost()));
                }
            } else if (OperType.SHOULD.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    for (Object it : (List)val)
                        bool.should(QueryBuilders.matchQuery(elasticQueryItem.name(), it).analyzer(AnalyzerType.smart).boost(elasticQueryItem.boost()));
                } else {
                    bool.should(QueryBuilders.matchQuery(elasticQueryItem.name(), val).analyzer(AnalyzerType.smart).boost(elasticQueryItem.boost()));
                }
            } else if (OperType.NOT.equals(elasticQueryItem.operType())){
                if (val instanceof List) {
                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    for (Object it : (List)val){
                        boolQueryBuilder.should(QueryBuilders.matchQuery(elasticQueryItem.name(), it).analyzer(AnalyzerType.smart).boost(elasticQueryItem.boost()));
                    }
                    bool.mustNot(boolQueryBuilder);
                } else {
                    bool.mustNot(QueryBuilders.matchQuery(elasticQueryItem.name(), val).analyzer(AnalyzerType.smart).boost(elasticQueryItem.boost()));
                }
            }
        }
    }

    protected void bindHighlight(ElasticHighlight highlight) {

    }

    @Override
    public Object convertValue(ConvertContext context) throws IllegalAccessException, InstantiationException {
        Object val = context.getVal();
        EntityCollectItem item = context.getItem();

        if (null != val){
            if (item.getField().isAnnotationPresent(ElasticQueryItem.class)) {
                ElasticQueryItem elasticQueryItem = item.getField().getAnnotation(ElasticQueryItem.class);
                bindOper(elasticQueryItem, val);
                if (elasticQueryItem.exclude()){
                    excludes.add(elasticQueryItem.name());
                }
            }

            if (item.getField().isAnnotationPresent(ElasticHighlight.class)) {
                ElasticHighlight elasticHighlight = item.getField().getAnnotation(ElasticHighlight.class);
                bindHighlight(elasticHighlight);
            }
        }

        return val;
    }

    public SearchSourceBuilder builder(){
        if (null != boolQueryBuilder){
            searchSourceBuilder.query(boolQueryBuilder);
        }

        if (null != boolFilterBuilder){
            searchSourceBuilder.postFilter(boolFilterBuilder);
        }

        if (null != highlightBuilder) {
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        if (excludes.size() > 0){
            searchSourceBuilder.fetchSource(null, excludes.toArray(new String[excludes.size()]));
        }

        return searchSourceBuilder;
    }
}
